# SERVER ROUTES
# -----------------------------------------------------------------------------
# All server routes should be defined in this file!

# Require models.
countdown = require "./../assets/js/models/countdown.coffee"
eggTimer = require "./../assets/js/models/eggTimer.coffee"

# Require app modules.
database = require "./database.coffee"
dataParser = require "./dataParser.coffee"
logger = require "./logger.coffee"
mail = require "./mail.coffee"
messages = require "./messages.coffee"
moment = require "moment"
settings = require "./settings.coffee"
twitter = require "./twitter.coffee"


# SET ROUTES
# -----------------------------------------------------------------------------

# Bind all routes.
exports.set = (app) ->

    # Content pages.
    app.get   "/",          exports.indexPage
    app.get   "/edit/:id",  exports.indexPage
    app.get   "/about",     exports.aboutPage
    app.get   "/stats",     exports.statsPage

    # JSON data: get and set [Countdown](countdown.html).
    app.get   "/db/countdown/:id",  exports.countdownGet
    app.post  "/db/countdown",      exports.countdownPost
    app.put   "/db/countdown",      exports.countdownPut

    # JSON data: get and set [Egg Timers](eggTimer.html).
    app.get   "/db/eggtimer/:id",  exports.eggTimerGet
    app.post  "/db/eggtimer",      exports.eggTimerPost

    # Twitter automated tasks.
    app.get   "twitter/checkinbox",      exports.twitterCheckInbox
    app.get   "twitter/checkscheduled",  exports.twitterCheckScheduled

    # Email processing.
    app.get   "/email/inbound",  exports.emailInboundGet
    app.post  "/email/inbound",  exports.emailInboundPost

    # Special countdowns and features.
    app.get   "/egg",        exports.eggTimerPage
    app.get   "/eggtimer",   exports.eggTimerPage
    app.get   "/xmas",       exports.countdownPageChristmas
    app.get   "/christmas",  exports.countdownPageChristmas

    # User generated countdowns.
    app.get   "/:id",  exports.countdownPage


# CONTENT PAGES
# -----------------------------------------------------------------------------

# The index / homepage.
exports.indexPage = (req, res) ->
    logRequest "indexPage"

    renderPage req, res, "index",
        title: settings.General.appTitle

# The "About" page
exports.aboutPage = (req, res) ->
    logRequest "aboutPage"

    renderPage req, res, "about",
        title: messages.about

# The "Stats" page
exports.statsPage = (req, res) ->
    logRequest "statsPage"

    database.countCountdown null, (err, result) ->
        renderPage req, res, "stats",
            title: messages.stats
            count: result


# COUNTDOWN JSON DATA
# -----------------------------------------------------------------------------

# Get a [Countdown](countdown.html) with the specified ID.
exports.countdownGet = (req, res) ->
    logRequest "countdownGet", req.params.id

    database.getCountdown {friendlyUrl: req.params.id}, (err, result) ->
        if result? and not err?
            res.json result
        else
            sendErrorResponse res, "Countdown GET", err

# Post a new [Countdown](countdown.html).
exports.countdownPost = (req, res) ->
    logRequest "countdownPost", req.body.title + " @ " + req.body.targetDate

    # Create a new countdown model and prepare it for DB insertion.
    cd = new countdown req.body
    prepareCountdownForDatabase cd, req

    # Save to the database. In case of error, send the error details back to the client.
    database.insertCountdown cd, (err, result) ->
        if err?
            sendErrorResponse res, "Countdown POST", err
        else
            result = result[0] if result.length > 0
            res.json result

            # Recreate the model and trigger the after creation events.
            cd = new countdown result
            afterCountdownCreated cd

# Update an existing [Countdown](countdown.html).
exports.countdownPut = (req, res) ->
    logRequest "countdownPut", req.body.id

    database.getCountdown {id: req.body.id}, (err, result) ->
        if not result? or err?
            sendErrorResponse res, "Countdown PUT", err
            return

        # Instance a [Countdown](countdown.html) model and
        # check permissions to edit the specified model.
        cd = new countdown result

        if not canEditCountdown cd, req
            res.sendErrorResponse res, 403, "You don't have permission to edit this countdown."
            return

        # Update values and remove uneditable countdown attributes.
        cd.set req.body
        cd.del "friendlyUrl"
        cd.del "viewCount"

        database.updateCountdown cd, (err2, result2) ->
            if err2?
                sendErrorResponse res, "Countdown PUT", err2
            else
                res.json cd.attributes


# EGG TIMER JSON DATA
# -----------------------------------------------------------------------------

# Get a [Egg Timer](eggTimer.html) with the specified ID.
exports.eggTimerGet = (req, res) ->
    logRequest "eggTimerGet", req.params.id

    database.getEggTimer {friendlyUrl: req.params.id}, (err, result) ->
        if result? and not err?
            res.json result
        else
            sendErrorResponse res, "EggTimer GET", err

# Post a new [Egg Timer](eggTimer.html).
exports.eggTimerPost = (req, res) ->
    logRequest "eggTimerPost", req.body.size + " " + req.body.yolk

    # Create a new egg timer model and prepare it for DB insertion.
    egg = new eggTimer req.body
    prepareModelForDatabase egg, req

    # Save to the database. In case of error, send the error details back to the client.
    database.insertEggTimer egg, (err, result) ->
        if err?
            sendErrorResponse res, "EggTimer POST", err
        else
            result = result[0] if result.length > 0
            res.json result


# CONTENT PAGES
# -----------------------------------------------------------------------------

# Automated task to check and parse Twitter direct messages and generate
# [Countdowns](countdown.html) out of them.
exports.twitterCheckInbox = (req, res) ->
    logRequest "twitterCheckInbox"

    twitter.checkInbox (err, result) ->
        if err?
            sendErrorResponse res, "EggTimer POST", err
        else
            res.send result

# Automated task to check for scheduled twitter direct messages to be
# dispatched. These are mainly related to countdown created by these users.
exports.twitterCheckScheduled = (req, res) ->
    logRequest "twitterCheckScheduled"

    twitter.checkInbox (err, result) ->
        if err?
            sendErrorResponse res, "EggTimer POST", err
        else
            res.send result


# EMAIL METHODS
# -----------------------------------------------------------------------------

# Inbound emails should be send with POST, so redirect the client to the
# homepage in case it's a GET request.
exports.emailInboundGet = (req, res) ->
    logRequest "emailInboundGet"

    res.redirect settings.General.appUrl

# Parses inbound emails and generate a new countdown.
exports.emailInboundPost = (req, res) ->
    logRequest "emailInboundPost", req.body

    # Get message from request body. If message wasn't found then
    # stop processing and log an error.
    try
        body = req.body.mandrill_events.pop()
        text = body.msg.text
    catch ex
        logger.error "Routes.emailInboundGet", "Could not find email message. Abort!", body
        return

    # Use the data parser to get a countdown out of the text.
    cd = dataParser.textToCountdown text

    # Make sure message was parsed successfully before proceeding.
    if not cd?
        if settings.General.debug
            logger.warn "Routes.emailInboundGet", "Could not parse messsage to generate countdown.", text
        return

    # Prepare countdown.
    prepareCountdownForDatabase cd, req

    # Force set creation source to `email`.
    cd.set "createdFrom", "email"

    # Append countdown data (title, creator name, etc) based on the incoming message.
    cd.set "title", msg["subject"] if msg["subject"]? and msg["subject"] isnt ""
    cd.set "creatorName", msg["from_name"] if msg["from_name"]? and msg["from_name"] isnt ""
    cd.set "creatorEmail", msg["from_email"] if msg["from_email"]? and msg["from_email"] isnt ""

    # Save to the database. In case of error, send the error details back to the client.
    database.insertCountdown cd, (err, result) ->
        if err?
            sendErrorResponse res, "EmailInbound POST", err
        else
            result = result[0] if result.length > 0
            res.send "OK"

            # Recreate the model and trigger the after creation events.
            cd = new countdown result
            afterCountdownCreated cd


# COUNTDOWNS AND UTILTY PAGES
# -----------------------------------------------------------------------------

# A special "Egg Timer" page.
exports.eggTimerPage = (req, res) ->
    logRequest "eggTimerPage", req.params.id

    renderPage req, res, "eggTimer",
        header: messages.eggTimer,
        title: messages.eggTimer + " - " + settings.General.appTitle

# A special "Time to Christmas" page.
exports.countdownPageChristmas = (req, res) ->
    logRequest "countdownPageChristmas", req.params.id

# The "Countdown view" page
exports.countdownPage = (req, res) ->
    logRequest "countdownPage", req.params.id

    database.getCountdown {friendlyUrl: req.params.id}, (err, result) ->
        if not err? and result? and result.length > 0
            now = moment()
            cd = new countdown result[0]

            title = cd.get "title"
            playSound = cd.getBool "playSound"

            viewCount = cd.get "viewCount"
            cd.set "viewCount", viewCount + 1

            creationDate = moment cd.get "creationDate"
            targetDate = moment cd.get "targetDate"
            duration = moment.duration targetDate.valueOf() - now.valueOf()

            # Editing will be enabled only if IP is the same as the creator's IP and
            # if the countdown was created less than a few hours ago.
            editEnabled = now.isBefore(targetDate) and canEditCountdown(cd, req)

            renderPage req, res, "countdown",
                title: title + " - " + settings.General.appTitle
                cd: cd
                creationDate: creationDate
                targetDate: targetDate
                editEnabled: editEnabled
                duration: duration
                playSound: playSound
                onEndMessage: dataParser.htmlLinkify cd.attributes.onEndMessage

            # After all done, increment the viewCount on the database.
            database.updateCountdown cd
        else
            res.status(404)
            renderPage req, res, "404",
                title: "Countdown does not exist!"
                friendlyUrl: req.params.id


# COUNTDOWN HELPER METHODS
# -----------------------------------------------------------------------------

# Prepare the passed [Countdown](countdown.html) model for database insertion.
prepareCountdownForDatabase = (cd, req) ->
    prepareModelForDatabase cd, req
    targetDate = moment cd.get "targetDate"
    cd.set "targetDate", targetDate.format settings.General.isoDateFormat

# Trigger actions after a countdown has been created on the database. For example
# post to Twitter or send an email to the creator. If countdown is private, skip this step.
afterCountdownCreated = (cd) ->
    return if cd.getBool "isPrivate"

    # Get necessary countdown attributes.
    creatorName = cd.get "creatorName"
    creatorEmail = cd.get "creatorEmail"
    createdFrom = cd.get "createdFrom"

    # Post to Twitter only if creator name is set.
    if creatorName? and creatorName isnt ""
        twitter.postCountdown cd

    # If created from email, reply to the user.
    if createdFrom is "email" and creatorEmail? and creatorEmail isnt ""
        mail.send cd, messages.countdownCreated, creatorEmail

# Check if user has permissions to edit a countdown, by check the client IP
# and the time when the counddown was created.
canEditCountdown = (cd, req) ->
    now = moment()
    creatorIP = cd.get "creatorIP"
    creationDate = moment cd.get "creationDate"
    return (moment.duration(now.valueOf() - creationDate.valueOf()).milliseconds() < 10800000) and (creatorIP is getClientIP req)


# GENERAL HELPER METHODS
# -----------------------------------------------------------------------------

# Prepare the passed model for database insertion.
# This will set some of its non-editable attributes like creation date and creator IP.
prepareModelForDatabase = (model, req) ->
    model.set "creationDate", moment().format settings.General.isoDateFormat
    model.set "creatorIP", getClientIP req
    model.set "createdFrom", getClientDevice req

# Helper to render pages.
renderPage = (req, res, file, options) ->
    options.device = getClientDevice req
    options.header = settings.General.pageHeader if not options.header? or options.header is ""
    res.render file, options

# Get the client's device identifier string based on the user agent.
getClientDevice = (req) ->
    ua = req.headers["user-agent"]

    # Find mobile devices.
    if ua.indexOf("Windows Phone 8") > 0
        return "wp8"
    if ua.indexOf("Windows Phone") > 0
        return "wp"
    if ua.indexOf("iPhone5") > 0
        return "iphone5"
    if ua.indexOf("iPhone4") > 0
        return "iphone4"
    if ua.indexOf("iPhone") > 0
        return "iphone"
    if ua.indexOf("Android 4") > 0
        return "android4"
    if ua.indexOf("Android") > 0
        return "android"

    # Return default desktop value if no mobile device found on user agent.
    return "desktop"

# Get the client / browser IP.
getClientIP = (req) ->
    try
        xfor = req.header("X-Forwarded-For")
        if xfor? and xfor isnt ""
            ip = xfor.split(",")[0]
        else
            ip = req.connection.remoteAddress
    catch ex
        ip = req.connection.remoteAddress
    return ip

# When the server can't return a valid result,
# send an error response with status code 500.
sendErrorResponse = (res, method, message) ->
    message = JSON.stringify message
    res.statusCode = 500
    res.send "Error: #{method} - #{message}"
    logger.error "HTTP Error", method, message

# Log the request to the console if `debug` is true.
logRequest = (method, params) ->
    if settings.General.debug
        if params?
            console.log "Request", method, params
        else
            console.log "Request", method